import tkinter as tk
from tkinter import filedialog, ttk, messagebox, simpledialog
from PIL import Image, ImageTk, ImageSequence
import os
import glob
import re


class ImageGifToolkit:
    def __init__(self, root):
        self.root = root
        self.root.title("图片GIF工具箱")
        # 优化界面大小并固定窗口尺寸
        self.root.geometry("1000x700")
        self.root.resizable(False, False)  # 不允许用户调整窗口大小

        # 窗口居中显示
        self.center_window()

        # 设置中文字体支持和样式
        self.style = ttk.Style()
        self.style.configure(".", font=("Microsoft", 10))
        self.style.configure("TNotebook", background="#f0f0f0")
        self.style.configure("TNotebook.Tab", font=("Microsoft", 10), padding=[15, 5])
        self.style.configure("Header.TLabel", font=("Microsoft", 12, "bold"))

        # 添加选中项的样式 - 明显的视觉差异
        self.style.configure("Accent.TFrame", background="#cce5ff", relief=tk.SUNKEN, borderwidth=2)
        self.style.map("Accent.TFrame", background=[("active", "#b8daff")])

        # 创建主选项卡
        self.notebook = ttk.Notebook(root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 图片转GIF选项卡
        self.to_gif_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.to_gif_frame, text="图片转GIF")

        # GIF转图片选项卡
        self.from_gif_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.from_gif_frame, text="GIF转图片")

        # 初始化图片转GIF功能
        self.init_to_gif_tab()

        # 初始化GIF转图片功能
        self.init_from_gif_tab()

    # 窗口居中显示
    def center_window(self):
        self.root.update_idletasks()
        width = self.root.winfo_width()
        height = self.root.winfo_height()
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry(f'{width}x{height}+{x}+{y}')

    # 初始化图片转GIF选项卡
    def init_to_gif_tab(self):
        # 存储加载的图片路径
        self.image_paths = []
        self.thumbnail_images = []
        self.dragging_index = None

        # 标题
        title_label = ttk.Label(self.to_gif_frame, text="图片转GIF转换器", style="Header.TLabel")
        title_label.pack(pady=10)

        # 图片路径选择区域
        path_frame = ttk.LabelFrame(self.to_gif_frame, text="图片来源", padding="10")
        path_frame.pack(fill=tk.X, padx=10, pady=5)

        ttk.Label(path_frame, text="图片路径:").pack(side=tk.LEFT, padx=5, pady=5)

        self.path_var = tk.StringVar()
        path_entry = ttk.Entry(path_frame, textvariable=self.path_var, width=50)
        path_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)

        browse_btn = ttk.Button(path_frame, text="浏览文件夹...", command=self.browse_image_path)
        browse_btn.pack(side=tk.LEFT, padx=5, pady=5)

        file_btn = ttk.Button(path_frame, text="选择图片...", command=self.select_image_files)
        file_btn.pack(side=tk.LEFT, padx=5, pady=5)

        # GIF设置区域
        settings_frame = ttk.LabelFrame(self.to_gif_frame, text="GIF设置", padding="10")
        settings_frame.pack(fill=tk.X, padx=10, pady=5)

        # 速度设置
        speed_frame = ttk.Frame(settings_frame)
        speed_frame.pack(side=tk.LEFT, padx=20, pady=5)

        ttk.Label(speed_frame, text="动画速度:").pack(side=tk.LEFT, padx=5)

        self.speed_var = tk.IntVar(value=10)
        speed_scale = ttk.Scale(speed_frame, from_=1, to=20, orient=tk.HORIZONTAL,
                                variable=self.speed_var, length=150, command=self.update_speed_label)
        speed_scale.pack(side=tk.LEFT, padx=5)

        self.speed_label = ttk.Label(speed_frame, text=str(self.speed_var.get()))
        self.speed_label.pack(side=tk.LEFT, padx=5)

        ttk.Label(speed_frame, text="(值越大速度越快)").pack(side=tk.LEFT, padx=5)

        # 循环次数设置
        loop_frame = ttk.Frame(settings_frame)
        loop_frame.pack(side=tk.LEFT, padx=20, pady=5)

        self.loop_var = tk.IntVar(value=0)  # 0表示无限循环
        self.loop_type = tk.StringVar(value="infinite")

        infinite_radio = ttk.Radiobutton(loop_frame, text="无限循环",
                                         variable=self.loop_type, value="infinite",
                                         command=self.toggle_loop_input)
        infinite_radio.pack(side=tk.LEFT, padx=5)

        finite_radio = ttk.Radiobutton(loop_frame, text="有限循环",
                                       variable=self.loop_type, value="finite",
                                       command=self.toggle_loop_input)
        finite_radio.pack(side=tk.LEFT, padx=5)

        self.loop_entry = ttk.Entry(loop_frame, textvariable=self.loop_var, width=5, state=tk.DISABLED)
        self.loop_entry.pack(side=tk.LEFT, padx=5)

        ttk.Label(loop_frame, text="次 (0=无限)").pack(side=tk.LEFT, padx=5)

        # 按钮区域
        btn_frame = ttk.Frame(self.to_gif_frame, padding="10")
        btn_frame.pack(fill=tk.X, padx=10)

        load_btn = ttk.Button(btn_frame, text="加载图片", command=self.load_images, width=15)
        load_btn.pack(side=tk.LEFT, padx=10)

        create_btn = ttk.Button(btn_frame, text="创建GIF", command=self.create_gif, width=15)
        create_btn.pack(side=tk.LEFT, padx=10)

        preview_btn = ttk.Button(btn_frame, text="预览", command=self.preview_gif, width=15)
        preview_btn.pack(side=tk.LEFT, padx=10)

        clear_btn = ttk.Button(btn_frame, text="清空列表", command=self.clear_image_list, width=15)
        clear_btn.pack(side=tk.LEFT, padx=10)

        # 排序按钮
        sort_frame = ttk.Frame(btn_frame)
        sort_frame.pack(side=tk.RIGHT, padx=10)

        up_btn = ttk.Button(sort_frame, text="上移", command=self.move_image_up, width=8)
        up_btn.pack(side=tk.LEFT, padx=5)

        down_btn = ttk.Button(sort_frame, text="下移", command=self.move_image_down, width=8)
        down_btn.pack(side=tk.LEFT, padx=5)

        # 图片列表区域
        list_frame = ttk.LabelFrame(self.to_gif_frame, text="图片列表: 0张图片", padding="10")
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.list_canvas = tk.Canvas(list_frame, bg="#ffffff", highlightthickness=1, highlightbackground="#cccccc")
        self.list_scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.list_canvas.yview)
        self.list_scrollable_frame = ttk.Frame(self.list_canvas)

        self.list_scrollable_frame.bind(
            "<Configure>",
            lambda e: self.list_canvas.configure(
                scrollregion=self.list_canvas.bbox("all")
            )
        )

        self.list_canvas.create_window((0, 0), window=self.list_scrollable_frame, anchor="nw")
        self.list_canvas.configure(yscrollcommand=self.list_scrollbar.set)

        # 绑定鼠标事件用于拖拽排序
        self.list_canvas.bind("<ButtonPress-1>", self.on_drag_start)
        self.list_canvas.bind("<B1-Motion>", self.on_drag_motion)
        self.list_canvas.bind("<ButtonRelease-1>", self.on_drag_end)

        # 绑定鼠标滚轮事件
        self.list_canvas.bind_all("<MouseWheel>", self.on_mouse_wheel)  # Windows
        self.list_canvas.bind_all("<Button-4>", self.on_mouse_wheel)  # Linux
        self.list_canvas.bind_all("<Button-5>", self.on_mouse_wheel)  # Linux

        # 绑定画布大小变化事件，用于自适应排列
        self.list_canvas.bind("<Configure>", self.on_canvas_configure)

        self.list_canvas.pack(side="left", fill="both", expand=True)
        self.list_scrollbar.pack(side="right", fill="y")

        # 更新列表标题
        self.image_list_frame = list_frame
        self.selected_index = None

    # 初始化GIF转图片选项卡
    def init_from_gif_tab(self):
        # 标题
        title_label = ttk.Label(self.from_gif_frame, text="GIF分解为图片", style="Header.TLabel")
        title_label.pack(pady=10)

        # GIF文件选择区域
        gif_file_frame = ttk.LabelFrame(self.from_gif_frame, text="GIF文件", padding="10")
        gif_file_frame.pack(fill=tk.X, padx=10, pady=5)

        ttk.Label(gif_file_frame, text="GIF路径:").pack(side=tk.LEFT, padx=5, pady=5)

        self.gif_path_var = tk.StringVar()
        gif_entry = ttk.Entry(gif_file_frame, textvariable=self.gif_path_var, width=50)
        gif_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)

        browse_gif_btn = ttk.Button(gif_file_frame, text="选择GIF...", command=self.select_gif_file)
        browse_gif_btn.pack(side=tk.LEFT, padx=5, pady=5)

        # 输出设置区域
        output_frame = ttk.LabelFrame(self.from_gif_frame, text="输出设置", padding="10")
        output_frame.pack(fill=tk.X, padx=10, pady=5)

        ttk.Label(output_frame, text="输出目录:").pack(side=tk.LEFT, padx=5, pady=5)

        self.output_dir_var = tk.StringVar()
        output_entry = ttk.Entry(output_frame, textvariable=self.output_dir_var, width=50)
        output_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)

        browse_output_btn = ttk.Button(output_frame, text="浏览目录...", command=self.select_output_dir)
        browse_output_btn.pack(side=tk.LEFT, padx=5, pady=5)

        # 文件名前缀设置
        prefix_frame = ttk.Frame(output_frame)
        prefix_frame.pack(side=tk.LEFT, padx=20, pady=5)

        ttk.Label(prefix_frame, text="文件名前缀:").pack(side=tk.LEFT, padx=5)

        self.prefix_var = tk.StringVar(value="frame_")
        prefix_entry = ttk.Entry(prefix_frame, textvariable=self.prefix_var, width=15)
        prefix_entry.pack(side=tk.LEFT, padx=5)

        # 格式选择
        format_frame = ttk.Frame(output_frame)
        format_frame.pack(side=tk.LEFT, padx=20, pady=5)

        ttk.Label(format_frame, text="输出格式:").pack(side=tk.LEFT, padx=5)

        self.format_var = tk.StringVar(value="png")
        format_combobox = ttk.Combobox(format_frame, textvariable=self.format_var, values=["png", "jpg", "jpeg", "bmp"],
                                       width=8)
        format_combobox.pack(side=tk.LEFT, padx=5)

        # 按钮区域
        btn_frame = ttk.Frame(self.from_gif_frame, padding="10")
        btn_frame.pack(fill=tk.X, padx=10)

        extract_btn = ttk.Button(btn_frame, text="分解GIF", command=self.extract_gif_frames, width=15)
        extract_btn.pack(side=tk.LEFT, padx=10)

        preview_gif_btn = ttk.Button(btn_frame, text="预览GIF", command=self.preview_source_gif, width=15)
        preview_gif_btn.pack(side=tk.LEFT, padx=10)

        open_folder_btn = ttk.Button(btn_frame, text="打开输出文件夹", command=self.open_output_folder, width=15)
        open_folder_btn.pack(side=tk.LEFT, padx=10)

        # 帧列表区域
        frame_list_frame = ttk.LabelFrame(self.from_gif_frame, text="GIF帧预览", padding="10")
        frame_list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.frame_canvas = tk.Canvas(frame_list_frame, bg="#ffffff", highlightthickness=1,
                                      highlightbackground="#cccccc")
        self.frame_scrollbar = ttk.Scrollbar(frame_list_frame, orient="vertical", command=self.frame_canvas.yview)
        self.frame_scrollable_frame = ttk.Frame(self.frame_canvas)

        self.frame_scrollable_frame.bind(
            "<Configure>",
            lambda e: self.frame_canvas.configure(
                scrollregion=self.frame_canvas.bbox("all")
            )
        )

        self.frame_canvas.create_window((0, 0), window=self.frame_scrollable_frame, anchor="nw")
        self.frame_canvas.configure(yscrollcommand=self.frame_scrollbar.set)

        # 绑定鼠标滚轮事件
        self.frame_canvas.bind_all("<MouseWheel>", self.on_frame_mouse_wheel)  # Windows
        self.frame_canvas.bind_all("<Button-4>", self.on_frame_mouse_wheel)  # Linux
        self.frame_canvas.bind_all("<Button-5>", self.on_frame_mouse_wheel)  # Linux

        # 绑定画布大小变化事件，用于自适应排列
        self.frame_canvas.bind("<Configure>", self.on_frame_canvas_configure)

        self.frame_canvas.pack(side="left", fill="both", expand=True)
        self.frame_scrollbar.pack(side="right", fill="y")

        # 存储帧预览图片
        self.frame_previews = []

    # 图片转GIF相关方法
    def update_speed_label(self, value):
        self.speed_label.config(text=str(int(float(value))))

    def toggle_loop_input(self):
        if self.loop_type.get() == "finite":
            self.loop_entry.config(state=tk.NORMAL)
        else:
            self.loop_entry.config(state=tk.DISABLED)

    def browse_image_path(self):
        path = filedialog.askdirectory()
        if path:
            self.path_var.set(path)

    def select_image_files(self):
        file_paths = filedialog.askopenfilenames(
            filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif")]
        )
        if file_paths:
            # 提取文件所在路径
            self.path_var.set(os.path.dirname(file_paths[0]))
            # 过滤已经加载的图片
            new_paths = [p for p in file_paths if p not in self.image_paths]
            self.image_paths.extend(new_paths)
            self.update_image_list()
            messagebox.showinfo("成功", f"已加载 {len(new_paths)} 张图片")

    def load_images(self):
        # 如果没有选择路径，使用文件选择对话框
        if not self.path_var.get():
            self.select_image_files()
        else:
            # 从指定路径加载所有图片
            path = self.path_var.get()
            if os.path.isdir(path):
                file_paths = glob.glob(os.path.join(path, "*.png")) + \
                             glob.glob(os.path.join(path, "*.jpg")) + \
                             glob.glob(os.path.join(path, "*.jpeg")) + \
                             glob.glob(os.path.join(path, "*.bmp")) + \
                             glob.glob(os.path.join(path, "*.gif"))

                # 按文件名排序
                file_paths.sort(key=lambda x: [int(c) if c.isdigit() else c.lower() for c in
                                               re.split('(\d+)', os.path.basename(x))])

                if file_paths:
                    # 过滤已经加载的图片
                    new_paths = [p for p in file_paths if p not in self.image_paths]
                    self.image_paths.extend(new_paths)
                    self.update_image_list()
                    messagebox.showinfo("成功", f"已加载 {len(new_paths)} 张图片")
                else:
                    messagebox.showinfo("提示", "该目录下没有找到图片文件")
            else:
                messagebox.showerror("错误", "无效的路径")
                return

    def update_image_list(self):
        # 清空现有列表
        for widget in self.list_scrollable_frame.winfo_children():
            widget.destroy()

        # 更新列表标题
        self.image_list_frame.config(text=f"图片列表: {len(self.image_paths)} 张图片")

        # 添加图片到列表
        self.thumbnail_images = []
        # 计算每行可显示的图片数量（根据画布宽度自适应）
        canvas_width = self.list_canvas.winfo_width()
        if canvas_width > 0:
            # 每张图片约150像素宽（120+30间距）
            images_per_row = max(1, canvas_width // 150)
        else:
            images_per_row = 5  # 默认值

        for i, path in enumerate(self.image_paths):
            # 根据是否选中使用不同的样式
            frame_style = "Accent.TFrame" if self.selected_index == i else ""
            frame = ttk.Frame(self.list_scrollable_frame, padding="5", relief=tk.RAISED, borderwidth=1,
                              style=frame_style)

            try:
                # 创建缩略图
                img = Image.open(path)
                img.thumbnail((120, 120))
                tk_img = ImageTk.PhotoImage(img)
                self.thumbnail_images.append(tk_img)  # 保持引用

                # 序号标签 - 选中项用不同颜色
                index_label = ttk.Label(
                    frame,
                    text=f"{i + 1}",
                    font=("Microsoft", 8, "bold"),
                    width=2,
                    foreground="blue" if self.selected_index == i else "black"
                )
                index_label.pack(anchor=tk.NW, padx=2, pady=2)

                img_label = ttk.Label(frame, image=tk_img)
                img_label.pack(pady=5)

                # 显示文件名
                name_label = ttk.Label(frame, text=os.path.basename(path), wraplength=110)
                name_label.pack()

                # 删除按钮 - 选中项按钮高亮
                del_btn = ttk.Button(
                    frame,
                    text="删除",
                    command=lambda idx=i: self.remove_image(idx),
                    width=8
                )
                del_btn.pack(pady=5)

                # 绑定点击事件用于选择
                frame.bind("<Button-1>", lambda e, idx=i: self.select_image(idx))

                # 排列图片（自适应每行数量）
                frame.grid(row=i // images_per_row, column=i % images_per_row, padx=8, pady=8)

            except Exception as e:
                ttk.Label(frame, text=f"无法加载图片\n{os.path.basename(path)}").pack(padx=5, pady=5)
                frame.grid(row=i // images_per_row, column=i % images_per_row, padx=8, pady=8)
                print(f"加载图片错误: {e}")

    # 拖拽排序相关方法
    def on_drag_start(self, event):
        # 获取点击位置对应的图片索引
        x, y = event.x, event.y
        widgets = self.list_scrollable_frame.winfo_children()

        for i, widget in enumerate(widgets):
            x1, y1, x2, y2 = widget.winfo_x(), widget.winfo_y(), \
                widget.winfo_x() + widget.winfo_width(), \
                widget.winfo_y() + widget.winfo_height()

            if x1 <= x <= x2 and y1 <= y <= y2:
                self.dragging_index = i
                self.select_image(i)
                break

    def on_drag_motion(self, event):
        # 拖拽过程中更新选中状态
        if self.dragging_index is not None:
            x, y = event.x, event.y
            widgets = self.list_scrollable_frame.winfo_children()

            for i, widget in enumerate(widgets):
                x1, y1, x2, y2 = widget.winfo_x(), widget.winfo_y(), \
                    widget.winfo_x() + widget.winfo_width(), \
                    widget.winfo_y() + widget.winfo_height()

                if x1 <= x <= x2 and y1 <= y <= y2 and i != self.dragging_index:
                    # 交换位置
                    self.image_paths[self.dragging_index], self.image_paths[i] = \
                        self.image_paths[i], self.image_paths[self.dragging_index]
                    # 更新拖拽索引
                    self.dragging_index = i
                    self.selected_index = i
                    self.update_image_list()
                    break

    def on_drag_end(self, event):
        self.dragging_index = None

    def select_image(self, index):
        self.selected_index = index
        self.update_image_list()

    def remove_image(self, index):
        if 0 <= index < len(self.image_paths):
            del self.image_paths[index]
            # 重置选中索引
            if self.selected_index == index:
                self.selected_index = None
            elif self.selected_index > index:
                self.selected_index -= 1
            self.update_image_list()

    def move_image_up(self):
        if self.selected_index is not None and self.selected_index > 0:
            # 交换位置
            self.image_paths[self.selected_index], self.image_paths[self.selected_index - 1] = \
                self.image_paths[self.selected_index - 1], self.image_paths[self.selected_index]
            # 更新选中索引
            self.selected_index -= 1
            self.update_image_list()

    def move_image_down(self):
        if self.selected_index is not None and self.selected_index < len(self.image_paths) - 1:
            # 交换位置
            self.image_paths[self.selected_index], self.image_paths[self.selected_index + 1] = \
                self.image_paths[self.selected_index + 1], self.image_paths[self.selected_index]
            # 更新选中索引
            self.selected_index += 1
            self.update_image_list()

    def create_gif(self):
        if not self.image_paths:
            messagebox.showwarning("警告", "请先加载图片")
            return

        # 获取保存路径
        save_path = filedialog.asksaveasfilename(
            defaultextension=".gif",
            filetypes=[("GIF文件", "*.gif")]
        )

        if not save_path:
            return

        try:
            # 打开所有图片
            images = []
            for path in self.image_paths:
                img = Image.open(path)
                # 转换为RGB模式，确保兼容性
                if img.mode in ('RGBA', 'LA'):
                    background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
                    background.paste(img, img.split()[-1])
                    img = background
                images.append(img.convert('RGB'))

            # 获取循环次数
            loop = self.loop_var.get() if self.loop_type.get() == "finite" else 0

            # 获取延迟时间 (值越大速度越慢，这里做个转换)
            delay = int(300 / self.speed_var.get())  # 300是基准值

            # 保存为GIF
            images[0].save(
                save_path,
                format='GIF',
                append_images=images[1:],
                save_all=True,
                duration=delay,
                loop=loop,
                disposal=2  # 每帧后清除画布
            )

            messagebox.showinfo("成功", f"GIF已保存到:\n{save_path}")

            # 询问是否打开文件
            if messagebox.askyesno("打开文件", "是否要打开生成的GIF文件?"):
                os.startfile(save_path)

        except Exception as e:
            messagebox.showerror("错误", f"创建GIF失败:\n{str(e)}")

    def preview_gif(self):
        if not self.image_paths:
            messagebox.showwarning("警告", "请先加载图片")
            return

        # 创建临时GIF用于预览
        try:
            temp_gif = "temp_preview.gif"

            # 打开所有图片
            images = []
            for path in self.image_paths:
                img = Image.open(path)
                if img.mode in ('RGBA', 'LA'):
                    background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
                    background.paste(img, img.split()[-1])
                    img = background
                images.append(img.convert('RGB'))

            # 获取循环次数和延迟
            loop = self.loop_var.get() if self.loop_type.get() == "finite" else 0
            delay = int(300 / self.speed_var.get())

            # 保存临时GIF
            images[0].save(
                temp_gif,
                format='GIF',
                append_images=images[1:],
                save_all=True,
                duration=delay,
                loop=loop,
                disposal=2
            )

            # 创建预览窗口
            preview_window = tk.Toplevel(self.root)
            preview_window.title("GIF预览")
            preview_window.geometry("600x500")
            preview_window.resizable(False, False)  # 固定预览窗口大小
            # 预览窗口居中
            self.center_toplevel(preview_window)

            # 显示GIF
            gif_label = ttk.Label(preview_window)
            gif_label.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

            # 播放GIF
            self.play_gif(temp_gif, gif_label, preview_window)

            # 关闭窗口时删除临时文件
            preview_window.protocol("WM_DELETE_WINDOW", lambda: self.close_preview(preview_window, temp_gif))

        except Exception as e:
            messagebox.showerror("错误", f"预览失败:\n{str(e)}")

    # 使子窗口居中显示
    def center_toplevel(self, toplevel):
        toplevel.update_idletasks()
        width = toplevel.winfo_width()
        height = toplevel.winfo_height()
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        toplevel.geometry(f'{width}x{height}+{x}+{y}')

    def play_gif(self, gif_path, label, window):
        try:
            # 打开GIF并播放
            gif = Image.open(gif_path)
            frames = []

            # 提取所有帧
            try:
                while True:
                    frames.append(ImageTk.PhotoImage(gif.copy()))
                    gif.seek(len(frames))  # 下一帧
            except EOFError:
                pass  # 所有帧已提取

            # 获取延迟时间
            delay = int(300 / self.speed_var.get())

            # 播放帧
            def update_frame(frame_idx):
                if window.winfo_exists():  # 检查窗口是否仍存在
                    frame = frames[frame_idx]
                    label.config(image=frame)
                    next_idx = (frame_idx + 1) % len(frames)
                    window.after(delay, update_frame, next_idx)

            if frames:
                update_frame(0)

        except Exception as e:
            print(f"播放GIF错误: {e}")

    def close_preview(self, window, temp_file):
        window.destroy()
        # 延迟删除临时文件，确保资源已释放
        self.root.after(100, lambda: self.delete_temp_file(temp_file))

    def delete_temp_file(self, temp_file):
        try:
            if os.path.exists(temp_file):
                os.remove(temp_file)
        except Exception as e:
            print(f"删除临时文件错误: {e}")

    def clear_image_list(self):
        if self.image_paths:
            if messagebox.askyesno("确认", "确定要清空图片列表吗?"):
                self.image_paths = []
                self.selected_index = None
                self.update_image_list()
        else:
            messagebox.showinfo("提示", "图片列表已经是空的")

    # GIF转图片相关方法
    def select_gif_file(self):
        file_path = filedialog.askopenfilename(
            filetypes=[("GIF文件", "*.gif")]
        )
        if file_path:
            self.gif_path_var.set(file_path)
            # 自动预览GIF帧
            self.preview_gif_frames()

    def select_output_dir(self):
        dir_path = filedialog.askdirectory()
        if dir_path:
            self.output_dir_var.set(dir_path)

    def preview_gif_frames(self):
        # 清空现有预览
        for widget in self.frame_scrollable_frame.winfo_children():
            widget.destroy()

        self.frame_previews = []
        gif_path = self.gif_path_var.get()

        if not gif_path or not os.path.exists(gif_path):
            return

        try:
            gif = Image.open(gif_path)
            frame_count = 0

            # 计算每行可显示的帧数量（根据画布宽度自适应）
            canvas_width = self.frame_canvas.winfo_width()
            if canvas_width > 0:
                # 每张图片约150像素宽（120+30间距）
                frames_per_row = max(1, canvas_width // 150)
            else:
                frames_per_row = 4  # 默认值

            # 如果未设置输出目录，自动设置为GIF所在目录
            if not self.output_dir_var.get():
                self.output_dir_var.set(os.path.dirname(gif_path))

            # 提取所有帧并预览
            for frame in ImageSequence.Iterator(gif):
                frame = frame.convert("RGB")
                frame.thumbnail((120, 120))
                tk_img = ImageTk.PhotoImage(frame)
                self.frame_previews.append(tk_img)

                frame = ttk.Frame(self.frame_scrollable_frame, padding="5", relief=tk.RAISED, borderwidth=1)

                # 序号标签
                index_label = ttk.Label(frame, text=f"{frame_count + 1}", font=("Microsoft", 8, "bold"), width=2)
                index_label.pack(anchor=tk.NW, padx=2, pady=2)

                img_label = ttk.Label(frame, image=tk_img)
                img_label.pack(pady=5)

                # 显示帧编号
                name_label = ttk.Label(frame, text=f"帧 {frame_count + 1}", wraplength=110)
                name_label.pack()

                # 排列帧（自适应每行数量）
                frame.grid(row=frame_count // frames_per_row, column=frame_count % frames_per_row, padx=8, pady=8)

                frame_count += 1

            # 更新标题
            self.from_gif_frame.winfo_children()[-1].config(text=f"GIF帧预览: 共 {frame_count} 帧")

        except Exception as e:
            messagebox.showerror("错误", f"预览GIF帧失败:\n{str(e)}")

    def preview_source_gif(self):
        gif_path = self.gif_path_var.get()
        if not gif_path or not os.path.exists(gif_path):
            messagebox.showwarning("警告", "请先选择有效的GIF文件")
            return

        # 创建预览窗口
        preview_window = tk.Toplevel(self.root)
        preview_window.title("GIF预览")
        preview_window.geometry("600x500")
        preview_window.resizable(False, False)  # 固定预览窗口大小
        # 预览窗口居中
        self.center_toplevel(preview_window)

        # 显示GIF
        gif_label = ttk.Label(preview_window)
        gif_label.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        # 播放GIF
        self.play_gif(gif_path, gif_label, preview_window)

    def extract_gif_frames(self):
        gif_path = self.gif_path_var.get()
        output_dir = self.output_dir_var.get()
        prefix = self.prefix_var.get()
        img_format = self.format_var.get()

        if not gif_path or not os.path.exists(gif_path):
            messagebox.showwarning("警告", "请先选择有效的GIF文件")
            return

        if not output_dir:
            messagebox.showwarning("警告", "请先选择输出目录")
            return

        if not prefix.strip():
            messagebox.showwarning("警告", "请输入文件名前缀")
            return

        try:
            # 创建输出目录（如果不存在）
            os.makedirs(output_dir, exist_ok=True)

            gif = Image.open(gif_path)
            frame_count = 0

            # 提取所有帧
            for frame in ImageSequence.Iterator(gif):
                frame = frame.convert("RGB")
                # 构建输出路径
                output_path = os.path.join(output_dir, f"{prefix}{frame_count:03d}.{img_format}")
                frame.save(output_path)
                frame_count += 1

            messagebox.showinfo("成功", f"已成功提取 {frame_count} 帧到:\n{output_dir}")

            # 询问是否打开文件夹
            if messagebox.askyesno("打开文件夹", "是否要打开输出文件夹?"):
                self.open_output_folder()

        except Exception as e:
            messagebox.showerror("错误", f"提取GIF帧失败:\n{str(e)}")

    def open_output_folder(self):
        output_dir = self.output_dir_var.get()
        if output_dir and os.path.exists(output_dir):
            os.startfile(output_dir)
        else:
            messagebox.showwarning("警告", "输出目录不存在")

    # 鼠标滚轮事件处理
    def on_mouse_wheel(self, event):
        # Windows系统
        if event.delta:
            self.list_canvas.yview_scroll(-1 * (event.delta // 120), "units")
        # Linux系统
        else:
            if event.num == 4:
                self.list_canvas.yview_scroll(-1, "units")
            elif event.num == 5:
                self.list_canvas.yview_scroll(1, "units")

    # GIF帧预览区域鼠标滚轮事件处理
    def on_frame_mouse_wheel(self, event):
        # Windows系统
        if event.delta:
            self.frame_canvas.yview_scroll(-1 * (event.delta // 120), "units")
        # Linux系统
        else:
            if event.num == 4:
                self.frame_canvas.yview_scroll(-1, "units")
            elif event.num == 5:
                self.frame_canvas.yview_scroll(1, "units")

    # 画布大小变化时重新排列图片
    def on_canvas_configure(self, event):
        self.update_image_list()

    # GIF帧画布大小变化时重新排列帧
    def on_frame_canvas_configure(self, event):
        self.preview_gif_frames()


if __name__ == "__main__":
    root = tk.Tk()
    # 设置中文字体
    root.option_add("*Font", "Microsoft 10")
    app = ImageGifToolkit(root)
    root.mainloop()